﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DAL;
using DAL.Repositories;

namespace BLL
{
    public class SeatCTR : Interfaces.ISeatCTR
    {
        SeatRepository _seatRep;

        public SeatCTR()
        {
            _seatRep = new SeatRepository();
        }

        #region bestSeatAlgoritme

        /// <summary>
        /// Find best seats in movie hall
        /// </summary>
        /// <param name="seatList">List of seats in movie hall</param>
        /// <param name="neededSeats">Number of seats needed</param>
        /// <param name="clickedSeat">Selected seats</param>
        /// <returns>List of best seats</returns>
        public IList<ScreenCast_Seat> GetBestSeats(List<ScreenCast_Seat> screenSeatList, int neededSeats, ScreenCast_Seat clickedSeat)
        {
            // get number of rows and seats
            int rows = screenSeatList.Max(ss => ss.Seat.Row);
            int columns = screenSeatList.Max(ss => ss.Seat.Number);
            // if clicked seat is null, calculate mid, else use clicked seat
            int rowMid = clickedSeat == null ? ((rows / 2) + (rows % 2)) : clickedSeat.Seat.Row;
            int seatMid = clickedSeat == null ? columns / 2 : clickedSeat.Seat.Number;

            // Get all collections of seats that's available in movie hall
            List<List<ScreenCast_Seat>> usefulSeatCollections = FindSuitingCollectionsOfFreeSeats(screenSeatList, neededSeats);
            List<ScreenCast_Seat> bestSeats = new List<ScreenCast_Seat>();

            // Check if there was any seat collections with at least "neededSeats"
            if (usefulSeatCollections.Count > 0)
            {
                // Select best collection
                bestSeats = GetSeatsAtBestRow(usefulSeatCollections, neededSeats, rowMid, seatMid, clickedSeat);
            }
            else
            {
                // Split needed seats 
                int firstHalf = (neededSeats / 2) + (neededSeats % 2);
                int secondHalf = neededSeats / 2;

                // Call this method again, to find free seat collections for each part
                bestSeats.AddRange(GetBestSeats(screenSeatList, firstHalf, clickedSeat));
                foreach (var item in bestSeats)
                {
                    item.SeatAvailable = false;
                }
                bestSeats.AddRange(GetBestSeats(screenSeatList, secondHalf, clickedSeat));
            }

            return bestSeats;
        }

        /// <summary>
        /// Find all collections of seats that's free and connected.
        /// </summary>
        /// <param name="seatList">List of all seats in hall</param>
        /// <param name="neededSeats">number of seats needed</param>
        /// <returns>List with nested lists of connected and free seats</returns>
        private List<List<ScreenCast_Seat>> FindSuitingCollectionsOfFreeSeats(List<ScreenCast_Seat> screenSeatList, int neededSeats)
        {
            List<List<ScreenCast_Seat>> freeSeatsInHall = new List<List<ScreenCast_Seat>>();

            // Getting ready for nested list of seats
            int seatCollectionCount = 0;
            freeSeatsInHall.Add(new List<ScreenCast_Seat>());

            ScreenCast_Seat oldSeat = null;

            // Start looking for free seats in a row
            foreach (ScreenCast_Seat ss in screenSeatList)
            {

                // Check if seat is free and is the seat after the last seat
                if (oldSeat != null && ss.SeatAvailable && ss.Seat.Number - 1 == oldSeat.Seat.Number)
                {
                    // Add seat to nested list
                    freeSeatsInHall[seatCollectionCount].Add(ss);
                    oldSeat = ss;
                }
                else // If new row (based on seat number) in hall, or if seat isn't the next 
                {
                    if (ss.SeatAvailable)
                    {
                        oldSeat = ss;

                        // If the collection of seats is smaller than what's needed, remove the collection from list
                        if (freeSeatsInHall[seatCollectionCount].Count() < neededSeats)
                        {
                            // remove nested list
                            freeSeatsInHall.RemoveAt(seatCollectionCount);
                        }
                        else
                        {
                            seatCollectionCount++;
                        }

                        // Add new nested list
                        freeSeatsInHall.Add(new List<ScreenCast_Seat>());
                        // add seat to nested list
                        freeSeatsInHall[seatCollectionCount].Add(ss);

                    }
                }
            }

            // Check if last added nested list fits the needed number of seats
            if (freeSeatsInHall[freeSeatsInHall.Count - 1].Count < neededSeats)
            {
                freeSeatsInHall.RemoveAt(seatCollectionCount);
            }

            return freeSeatsInHall;
        }

        /// <summary>
        /// Find the collection of seats with the seat that is closest to center of hall
        /// </summary>
        /// <param name="usefulSeatCollections">List of collections of seats that's big enough</param>
        /// <param name="neededSeats">Number of needed seats</param>
        /// <param name="rows">Number of rows in hall</param>
        /// <param name="columns">Number of columns in hall</param>
        /// <returns>Best collection of free seats</returns>
        private List<ScreenCast_Seat> GetSeatsAtBestRow(List<List<ScreenCast_Seat>> usefulSeatCollections, int neededSeats, int rowMid, int seatMid, ScreenCast_Seat clickedSeat)
        {
            List<ScreenCast_Seat> currentSeatsClosestToMid = new List<ScreenCast_Seat>();
            // set start value as higest possible to be sure to get number lower at first
            int currentDistanceFromMid = int.MaxValue;
            int currentDistanceFromMidRows = int.MaxValue;

            foreach (List<ScreenCast_Seat> ls in usefulSeatCollections)
            {
                // Check every seat's position relative to center of hall
                foreach (ScreenCast_Seat ss in ls)
                {
                    // If there is a seat closer to center than former once, the seat collection will be changed to the new one
                    if (Math.Abs(seatMid - ss.Seat.Number) <= currentDistanceFromMid)
                    {
                        if (Math.Abs(rowMid - ss.Seat.Row) <= currentDistanceFromMidRows)
                        {
                            // Add new distance from center and save seat collection
                            currentDistanceFromMid = Math.Abs(seatMid - ss.Seat.Number);
                            currentDistanceFromMidRows = Math.Abs(rowMid - ss.Seat.Row);
                            currentSeatsClosestToMid = ls;
                        }
                    }
                }
            }

            // Select needed seats from seat collection
            return SelectNeededSeats(currentSeatsClosestToMid, seatMid, neededSeats);
        }

        /// <summary>
        /// Select actual best seats from the best collection of free seats
        /// </summary>
        /// <param name="currentSeatsClosestToMid">Collection of free seats closest to center</param>
        /// <param name="seatMid">Number of seat in middle of a row</param>
        /// <param name="neededSeats">Number of needed seats</param>
        /// <returns>List of best seats</returns>
        private List<ScreenCast_Seat> SelectNeededSeats(List<ScreenCast_Seat> currentSeatsClosestToMid, int seatMid, int neededSeats)
        {
            List<ScreenCast_Seat> selectedSeats = new List<ScreenCast_Seat>();
            int seatCounter = 0;

            // Check if seats are to the right of center
            if (currentSeatsClosestToMid[0].Seat.Number > seatMid)
            {
                // Select seats from lowsest number in list until needed seats are selected
                while (neededSeats > seatCounter)
                {
                    selectedSeats.Add(currentSeatsClosestToMid[seatCounter++]);// add one to counter after called
                }
            }
            // Check if seats are to the left of center
            else if (currentSeatsClosestToMid[currentSeatsClosestToMid.Count - 1].Seat.Number < seatMid)
            {
                // Select seats from highest number in list until needed seats are selected
                while (neededSeats > seatCounter)
                {
                    selectedSeats.Add(currentSeatsClosestToMid[(currentSeatsClosestToMid.Count - 1) - seatCounter++]);// add one to counter after called
                }
            }
            // If center is part of seat collection
            else
            {
                // Find the seat in center, and its location in list
                ScreenCast_Seat centerSeat = currentSeatsClosestToMid.Single(cs => cs.Seat.Number == seatMid);
                int locationInSeatCollection = currentSeatsClosestToMid.IndexOf(centerSeat);

                // Add center seat to list of best seats
                selectedSeats.Add(centerSeat);
                seatCounter++;

                int index = 0;
                while (neededSeats > seatCounter)
                {
                    index++;

                    // Select seat to the right of center, if any
                    if ((locationInSeatCollection + index) < currentSeatsClosestToMid.Count)
                    {
                        selectedSeats.Add(currentSeatsClosestToMid[locationInSeatCollection + index]);
                        seatCounter++;
                    }

                    // Select seat to the left of center, if any
                    if (neededSeats > seatCounter && (locationInSeatCollection - index) >= 0)
                    {
                        selectedSeats.Add(currentSeatsClosestToMid[locationInSeatCollection - index]);
                        seatCounter++;
                    }

                }
            }

            return selectedSeats;
        }
        #endregion

        /// <summary>
        /// Change availability of seats, either from taken to free or from free to taken
        /// </summary>
        /// <param name="screenSeats">List of screen cast seat objects</param>
        /// <exception cref="InvalidOperationException"></exception>
        /// <exception cref="Exception"></exception>
        public void ChangeSeatsAvailability(List<ScreenCast_Seat> screenSeats)
        {
            _seatRep.ChangeSeatsAvailability(screenSeats);
        }

        /// <summary>
        /// Get seats status object for a screen cast
        /// </summary>
        /// <param name="screenCast">Screen cast to get seats objects from</param>
        /// <returns>List of all screen cast seats</returns>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="Exception"></exception>
        public IList<ScreenCast_Seat> GetScreenCastSeats(ScreenCast screenCast)
        {
            return _seatRep.GetScreenCastSeats(screenCast);
        }

        /// <summary>
        /// Get seats by movie hall
        /// </summary>
        /// <param name="movieHall">movie hall object</param>
        /// <returns>List of seats in a movie hall</returns>
        /// <exception cref="ArgumentNullException">Movie hall object is null or not existing in db</exception>
        /// <exception cref="Exception"></exception>
        public IList<Seat> GetSeats(MovieHall movieHall)
        {
            return _seatRep.GetSeats(movieHall);
        }
    }
}
